﻿(function () {

    window.crucible = window.crucible || {};
    var crucible = window.crucible;



    crucible.NodeManager = function () {
        this.rootNode = null;
        this.nodeCache = {};
    };

    crucible.NodeManager.prototype = function () {

        var init = function () {

                var dataDiv = $('#dataDiv');
                if (dataDiv.length) {
                    buildFromDataDiv.call(this, dataDiv);
                }
            },

            buildFromDataDiv = function(dataDiv) {

                var userId = dataDiv.data('userid');
                crucible.userId = userId;

                var treeData = dataDiv.data('argument');

                if (treeData) {
                    this.integrateTreeData(treeData.nodes, treeData.edges, treeData.root);
                    this.rootNode.freshData(treeData.root, treeData.rootParents);
                } else {
                    buildWithStarterNode.call(this);
                }
            },
            updateCache = function(serverNodes, rootId) {
                // merge into the this.nodeCache
                var i,
                    node,
                    nodeCache = this.nodeCache,
                    serverNode;

                for (i = 0; i < serverNodes.length; i++) {
                    serverNode = serverNodes[i];
                    node = nodeCache[serverNode.Id];
                    if (node) {
                        node.freshData(serverNode);
                    } else {
                        if (serverNode.Id === rootId && this.rootNode && this.rootNode.Id === "starter") {
                            this.rootNode.freshData(serverNode);
                            nodeCache[serverNode.Id] = this.rootNode;
                            document.getElementById('nodestarter').id = 'node' + serverNode.Id; // nodestarter is a later concatenation of "node" + id
                            //this.rootNode.elementId = 'node' + serverNode.Id;
                            //this.rootNode.setEl();
                            //crucible.treeMakerViewModel.removeFloaters([]);
                            delete nodeCache["starter"];
                        } else {
                            node = new crucible.TreeNode(serverNode);
                            nodeCache[serverNode.Id] = node;
                        }
                    }
                }

                if (rootId && !this.rootNode) {
                    this.rootNode = nodeCache[rootId];
                }
            },
            updateRelationships = function(edges, startingNode) {
                // establish parent-child relationships
                var j,
                    closedSet = [],
                    cursor,
                    openSet = [];

                if (!startingNode) {
                    startingNode = this.rootNode;
                }

                openSet.push(startingNode);

                while (openSet.length > 0) {
                    var open = openSet.pop();
                    closedSet.push(open);
                    removeChildren(open);
                    var childEdges = edges.filter(function(e) { return e.ParentId === open.Id; });
                    for (j = 0; j < childEdges.length; j++) {
                        var id = childEdges[j].ChildId;
                        //var node = first(nodes, function (n) { return n.id == id; });
                        var node = this.nodeCache[id];
                        if (!node) {
                            console.log("Broken edge! " + id);
                        }
                        if (closedSet.indexOf(node) >= 0) {
                            cursor = open.parent;
                            while (cursor) {
                                if (cursor === node) {
                                    crucible.raiseError("Infinite loop detected!", true);
                                    break;
                                }
                                cursor = cursor.parent;
                            }
                            
                            cloneNode(node, open);
                            continue;
                        }
                        if (node.parent && node.parent !== open) {
                            cloneNode(node, open);

                            //if (node.parent === open) {
                            //    // this relationship must have been set up in an earlier run
                            //    //node.Children.removeAll();
                            //    openSet.push(node); // keep going to pick up any new relationships.
                            //} else {
                            //    // this node is already in the tree under a different parent, so make a clone.
                            //    cloneNode(node, open);
                            //}
                        } else {
                            node.parent = open;
                            openSet.push(node);
                            open.Children.push(node);
                        }
                    }
                    
                    if ((open.Type() == crucible.NodeType.interpretation || open.Type() == crucible.NodeType.source) && open.Children().length) {
                        open.Source(open.Children()[0]);
                        // consider removing any remaining children because interpretations and sources are supposed to have 1 max.
                    }
                }
                
                function cloneNode(n, parent) {
                    n.cloneList = n.cloneList || [];
                    var clone = first(n.cloneList, function(c) { return c.parent === parent; });
                    if (!clone) {
                        clone = n.clone(parent.Id);
                        n.cloneList.push(clone);
                        clone.parent = parent;
                    }
                    parent.Children.push(clone);
                }
                
                function removeChildren(n) {
                    for (var i = 0; i < n.Children().length; i++) {
                        var c = n.Children()[i];
                        //if (c.cloneOf) {
                        //    remove(c.cloneOf.cloneList, function(cl) {
                        //        return cl === c;
                        //    });
                        //}
                        //c.parent = null;
                    }
                    n.Children.removeAll();
                }

            },
            integrateTreeData = function(serverNodes, edges, root) {

                var rootId = root.Id;

                if (this.rootNode && this.rootNode.Id === "starter") {
                    document.location.hash = rootId;
                }

                updateCache.call(this, serverNodes, rootId);

                updateRelationships.call(this, edges, this.nodeCache[rootId]);

            },
            buildWithStarterNode = function() {

                var starter = { Id: "starter", Title: "Untitled", Published: false };
                var nodes = [starter];

                this.integrateTreeData(nodes, [], starter);

            },
            addSourceStub = function (stubData, parentNode) {

                parentNode = this.nodeCache[parentNode.Id];

                //parentNode.Children.removeAll();

                var serverNodes = [stubData];
                var edges = [{ ParentId: parentNode.Id, ChildId: stubData.Id }];
                var root = parentNode;
                this.integrateTreeData(serverNodes, edges, root);
                return parentNode.Source();
            },
            reRoot = function (id, focused, callback) {
                var self = this;
                focused = self.nodeCache[focused.Id];
                var newParent = self.nodeCache[id];
                if (newParent) {
                    var newRoot = newParent;
                    while (newRoot.parent) {
                        newRoot = newRoot.parent;
                    }
                    self.rootNode = newRoot;
                    fixClones(self, focused, newParent);
                    if (callback) {
                        callback(newParent);
                    }
                } else {
                    crucible.datacontext.fetchTree(id, function(treeData) {
                        self.rootNode = null;
                        self.integrateTreeData(treeData.nodes, treeData.edges, treeData.root);
                        // swap clones as necessary.
                        newParent = self.nodeCache[id];
                        fixClones(self, focused, newParent);
                        
                        if (callback) {
                            callback(newParent);
                        }
                    });

                }
            };

        function fixClones(self, real, newParent) {
            // make sure the new parent is given the original node, not a clone
            var childIndex = newParent.Children.indexOf(real);
            if (!(childIndex >= 0)) {
                // it must have a clone for a child. swap!
                var cloneMatches = newParent.Children.remove(function (item) { return item.Id === real.Id; });
                if (cloneMatches && cloneMatches.length) {
                    var clone = cloneMatches[0];
                    swapClone(real, clone);
                }

            }
            
            // make sure the tree has no clones without originals
            for (var nodeId in self.nodeCache) {
                var node = self.nodeCache[nodeId];
                if (node.cloneList && !isRooted(node, self.rootNode)) {
                    for (var i = 0; i < node.cloneList.length; i++) {
                        clone = node.cloneList[i];
                        if (isRooted(clone, self.rootNode)) {
                            //swap
                            swapClone(node, clone);
                            break;
                        }
                    }
                }
            }
        }
        
        function swapClone(real, clone) {
            var newParent = clone.parent;
            real.parent.Children.remove(real);
            real.parent.Children.push(clone);
            clone.parent = real.parent;
            newParent.Children.remove(clone);
            newParent.Children.push(real);
            real.parent = newParent;
        }
        
        function isRooted(node, rootNode) {
            while (node) {
                if (node === rootNode) return true;
                node = node.parent;
            }
            return false;
        }

        function first(array, predicate) {
            var i;
            for (i = 0; i < array.length; i++) {
                if (predicate(array[i])) {
                    return array[i];
                }
            }
            return null;
        }
        
        function remove(array, predicate) {
            var i,
                ret = null;
            for (i = 0; i < array.length; i++) {
                if (predicate(array[i])) {
                    ret = array[i];
                    array.splice(i, 1);
                }
            }
            return ret;
        }

        return {
            integrateTreeData: integrateTreeData,
            init: init,
            addSourceStub: addSourceStub,
            reRoot: reRoot
        };

    }();


}());